package vn.hut.soict.cs.phanquanghuy.neslegend.emulator.cpu;

import org.omg.CORBA.OMGVMCID;

import vn.hut.soict.cs.phanquanghuy.neslegend.emulator.memory.CPURam;
import vn.hut.soict.cs.phanquanghuy.neslegend.emulator.memory.Rom;
import vn.hut.soict.cs.phanquanghuy.neslegend.emulator.util.NumberUtil;
import vn.hut.soict.cs.phanquanghuy.neslegend.emulator.util.RefBoolean;

public class CPU6502 implements Runnable {
	//......................................................
	//REGISTERS FILE
	//......................................................
	
	private int register_pc;
	private int register_sp;
	private int register_a;
	private int register_x;
	private int register_y;
	
	//Register getter
	public int getPC() {
		return this.register_pc;
	}
	
	public int getSP() {
		return this.register_sp;
	}
	
	public int getA() {
		return this.register_a;
	}
	
	public int getX() {
		return this.register_x;
	}
	
	public int getY() {
		return this.register_y;
	}

	
	//Register flags;
	//Processor status
	
	//Flag layout
	// |N|V| |B|D|I|Z|C|
	//  7 6 5 4 3 2 1 0
	
	private boolean flag_carry; //Flag C
	private boolean flag_zero; //Flag Z
	private boolean flag_interrupt_disable; //Flag I
	private boolean flag_decimal_mode; //Flag D
	private boolean flag_break_command; //Flag B
	private boolean flag_overflow; //Flag V
	private boolean flag_negative; //Flag N
	
	public int getFlagsByte() {
		return    (flag_carry 				? NumberUtil.BIT0 : 0)
				| (flag_zero 				? NumberUtil.BIT1 : 0)
				| (flag_interrupt_disable	? NumberUtil.BIT2 : 0)
				| (flag_decimal_mode		? NumberUtil.BIT3 : 0)
				| (flag_break_command		? NumberUtil.BIT4 : 0)
				| 0 //Non flag
				| (flag_overflow			? NumberUtil.BIT6 : 0)
				| (flag_negative			? NumberUtil.BIT7 : 0);
	}
	
	private void setFlagsByte(final int flagsByte) {
		flag_carry 				= NumberUtil.getBit(flagsByte, 0);
		flag_zero		 		= NumberUtil.getBit(flagsByte, 1);
		flag_interrupt_disable 	= NumberUtil.getBit(flagsByte, 2);
		flag_decimal_mode 		= NumberUtil.getBit(flagsByte, 3);
		flag_break_command 		= NumberUtil.getBit(flagsByte, 4);
		flag_overflow 			= NumberUtil.getBit(flagsByte, 6);
		flag_negative 			= NumberUtil.getBit(flagsByte, 7);
	}
	
	public String flagsString() {
		return String.format("FLAGS: C = %b Z = %b I = %b D = %b B = %b V = %b N = %b", 
				flag_carry,
				flag_zero,
				flag_interrupt_disable,
				flag_decimal_mode,
				flag_break_command,
				flag_overflow,
				flag_negative);
	}
	
	//.................................................................
	//ADDRESS MODE
	//.................................................................
	
	private CPURam ram;
	private Rom programRom;
	
	public CPU6502(CPURam ram, Rom programRom) {
		this.ram = ram;
		this.programRom = programRom;
	}
	
	//Zero Page Mode
	//Command Type : OPCODE $ADDR
	private void writeZeroPage(int address, int value) {
		ram.writeMemory(address, value);
	}
	
	private int readZeroPage(int address) {
		return ram.readMemory(address);
	}
	
	//Zero Page Index Mode
	
	//Index X
	//Command Type : OPCODE $ADDR,X
	private void writeZeroPageX(int address, int value) {
		ram.writeMemory(address + register_x, value);
	}
	
	private int readZeroPageX(int address) {
		return ram.readMemory(address + register_x);
	}
	
	//Index Y
	//Command Type : OPCODE $ADDR,Y
	private void writeZeroPageY(int address, int value) {
		ram.writeMemory(address + register_y, value);
	}
	
	private int readZeroPageY(int address) {
		return ram.readMemory(address + register_y);
	}
	
	//Absolute Mode
	//Command Type : OPCODE $ADDR(word)
	private void writeAbsolute(int lowByteAddress, int highByteAddress, 
			int value) {
		ram.writeMemory(NumberUtil.makeWord(lowByteAddress, highByteAddress), value);
	}
	
	private int readAbsolute(int lowByteAddress, int highByteAddress) {
		return ram.readMemory(NumberUtil.makeWord(lowByteAddress, highByteAddress));
	}
	
	//Page Boundary Crossing
	//Memory Page is 256byte
	//CPU will take extra cycle if memory ready out of page
	
	//Absoulute Index Mode
	
	//Index X
	//Command Type : OPCODE $ADDR(word), X
	private void writeAbsoluteX(int lowByteAddress, int highByteAddress, 
			int value) {
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		
		ram.writeMemory(compoundWordPC + register_x, value);
	}
	
	private int readAbsoluteX(int lowByteAddress, int highByteAddress,
			RefBoolean isPageCrossed) {
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		int physicAddress = compoundWordPC + register_x;
		
		isPageCrossed.setValue(NumberUtil.checkCrossedPage(compoundWordPC, physicAddress));
		
		return ram.readMemory(physicAddress);
	}
	
	//Index Y
	//Command Type : OPCODE $ADDR(word), Y
	private void writeAbsoluteY(int lowByteAddress, int highByteAddress,
			int value) {
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		
		ram.writeMemory(compoundWordPC + register_y, value);
	}
	
	
	
	private int readAbsoluteY(int lowByteAddress, int highByteAddress,
			RefBoolean isPageCrossed) {
		
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		int physicAdress = compoundWordPC + register_y;
		
		isPageCrossed.setValue(NumberUtil.checkCrossedPage(compoundWordPC, physicAdress));
		
		return ram.readMemory(physicAdress);
	}
	
	//Indirect Mode
	//Command Type : OPCODE ($ADDR16bit)
	private void writeIndirect(int lowByteAddress, int highByteAddress,
			int value) {
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		int lowAddrress = ram.readMemory(compoundWordPC);
		int highAddress = ram.readMemory(compoundWordPC + 1);
		
		int physicAddress = NumberUtil.makeWord(lowAddrress, highAddress);
		
		ram.writeMemory(physicAddress, value);
	}
	
	private int readIndirect(int lowByteAddress, int highByteAddress) {
		int compoundWordPC = NumberUtil.makeWord(lowByteAddress, highByteAddress);
		int lowAddrress = ram.readMemory(compoundWordPC);
		int highAddress = ram.readMemory(compoundWordPC + 1);
		
		int physicAddress = NumberUtil.makeWord(lowAddrress, highAddress);
		
		return ram.readMemory(physicAddress);
	}
	
	//Index Indirect Mode
	
	//Index X 
	//Command type : OPCODE ($BYTE_ADDRESS, X)
	private void writeIndirectX(int byteAddress, int value) {
		int lowAddress = ram.readMemory(byteAddress + register_x);
		int highAddress = ram.readMemory(byteAddress + register_x + 1);
		
		int physicAddress = NumberUtil.makeWord(lowAddress, highAddress);
		
		ram.writeMemory(physicAddress, value);
	}
	
	private int readIndirectX(int byteAddress) {
		int lowAddress = ram.readMemory(byteAddress + register_x);
		int highAddress = ram.readMemory(byteAddress + register_x + 1);
		
		int physicAddress = NumberUtil.makeWord(lowAddress, highAddress);
		
		return ram.readMemory(physicAddress);
	}
	
	//Index Y
	//Command type : OPCODE ($BYTE_ADDRESS), Y
	private void writeIndirectY(int byteAddress, int value) {
		int lowAddress = ram.readMemory(byteAddress);
		int highAddress = ram.readMemory(byteAddress + 1);
		
		int compoundAddress = NumberUtil.makeWord(lowAddress, highAddress);
		
		ram.writeMemory(compoundAddress + register_y, value);
	}
	
	private int readIndirectY(int byteAddress, RefBoolean isPageCrossed) {
		int lowAddress = ram.readMemory(byteAddress);
		int highAddress = ram.readMemory(byteAddress + 1);
		
		int compoundAddress = NumberUtil.makeWord(lowAddress, highAddress);
		int physicAddress = compoundAddress + register_y;
		
		isPageCrossed.setValue(NumberUtil.checkCrossedPage(compoundAddress, physicAddress));
		
		return ram.readMemory(physicAddress);
	}
	
	private int cycles = 0;
	
	@Override
	public void run() {
		int instruction_length = 0;
		cycles = 0;
		
		final int commandOpcode = programRom.readMemory(register_pc);
		
		switch (commandOpcode) {
		//*** ADC
		case Instruction.OPCODE_ADC_IMMEDIATE:
			adc(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles = 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ADC_ZERO_PAGE:
			adc(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ADC_ZERO_PAGE_X:
			adc(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ADC_ABSOLUTE:
			adc(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_ADC_ABSOLUTE_X:
			adc(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_ADC_ABSOLUTE_Y:
			adc(AddressMode.ADDRESS_MODE_ABSOLUTE_Y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_ADC_INDIRECT_X:
			adc(AddressMode.ADDRESS_MODE_INDIRECT_X);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ADC_INDIRECT_Y:
			adc(AddressMode.ADDRESS_MODE_INDIRECT_Y);
			cycles += 5;
			instruction_length = 2;
			break;
		
		//*** AND
		case Instruction.OPCODE_AND_IMMEDIATE:
			and(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_AND_ZERO_PAGE:
			and(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_AND_ZERO_PAGE_X:
			and(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_AND_ABSOLUTE:
			and(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_AND_ABSOLUTE_X:
			and(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_AND_ABSOLUTE_Y:
			and(AddressMode.ADDRESS_MODE_ABSOLUTE_Y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_AND_INDIRECT_X:
			and(AddressMode.ADDRESS_MODE_INDIRECT_X);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_AND_INDIRECT_Y:
			and(AddressMode.ADDRESS_MODE_INDIRECT_Y);
			cycles += 5;
			instruction_length = 2;
			break;
			
		//*** ASL
		case Instruction.OPCODE_ASL_ACCUMULATOR:
			asl(AddressMode.ADDRESS_MODE_ACCUMULATOR);
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_ASL_ZERO_PAGE:
			asl(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ASL_ZERO_PAGE_X:
			asl(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_ASL_ABSOLUTE:
			asl(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 6;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_ASL_ABSOLUTE_X:
			asl(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 7;
			instruction_length = 3;
			break;
			
		//*** BCC
		case Instruction.OPCODE_BCC:
			register_pc += branch(!flag_carry);
			cycles += 2;
			instruction_length = 2;
			break;
			
		//*** BCS
		case Instruction.OPCODE_BCS:
			register_pc += branch(flag_carry);
			cycles += 2;
			instruction_length = 2;
			break;
			
		//*** BEQ
		case Instruction.OPCODE_BEQ:
			register_pc += branch(flag_zero);
			cycles += 2;
			instruction_length = 2;
			break;
			
		//*** BIT
		case Instruction.OPCODE_BIT_ZERO_PAGE:
			bit(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_BIT_ABSOLUTE:
			bit(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_BMI:
			register_pc += branch(flag_negative);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_BNE:
			register_pc += branch(!flag_zero);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_BPL:
			register_pc += branch(!flag_negative);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_BRK:
			// TO DO : Interrupt
			break;
			
		case Instruction.OPCODE_BVC:
			register_pc += branch(!flag_overflow);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_BVS:
			register_pc += branch(flag_overflow);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CLC:
			flag_carry = false;
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_CLD:
			flag_decimal_mode = false;
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_CLI:
			flag_interrupt_disable = false;
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_CLV:
			flag_overflow = false;
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_CMP_IMMEDIATE:
			compare(AddressMode.ADDRESS_MODE_IMMIDIATE, register_a);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CMP_ZERO_PAGE:
			compare(AddressMode.ADDRESS_MODE_ZEROPAGE, register_a);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CMP_ZERO_PAGE_X:
			compare(AddressMode.ADDRESS_MODE_ZEROPAGE_X, register_a);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CMP_ABSOLUTE:
			compare(AddressMode.ADDRESS_MODE_ABSOLUTE, register_a);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_CMP_ABSOLUTE_X:
			compare(AddressMode.ADDRESS_MODE_ABSOLUTE_X, register_a);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_CMP_ABSOLUTE_Y:
			compare(AddressMode.ADDRESS_MODE_ABSOLUTE_Y, register_a);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_CMP_INDIRECT_X:
			compare(AddressMode.ADDRESS_MODE_INDIRECT_X, register_a);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CMP_INDIRECT_Y:
			compare(AddressMode.ADDRESS_MODE_INDIRECT_Y, register_a);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CPX_IMMEDIATE:
			compare(AddressMode.ADDRESS_MODE_IMMIDIATE, register_x);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CPX_ZERO_PAGE:
			compare(AddressMode.ADDRESS_MODE_ZEROPAGE, register_x);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CPX_ABSOLUTE:
			compare(AddressMode.ADDRESS_MODE_ABSOLUTE, register_x);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_CPY_IMMEDIATE:
			compare(AddressMode.ADDRESS_MODE_IMMIDIATE, register_y);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CPY_ZERO_PAGE:
			compare(AddressMode.ADDRESS_MODE_ZEROPAGE, register_y);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_CPY_ABSOLUTE:
			compare(AddressMode.ADDRESS_MODE_ABSOLUTE, register_y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_DEC_ZERO_PAGE:
			modifyMemory(AddressMode.ADDRESS_MODE_ZEROPAGE, -1);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_DEC_ZERO_PAGE_X:
			modifyMemory(AddressMode.ADDRESS_MODE_ZEROPAGE_X, -1);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_DEC_ABSOLUTE:
			modifyMemory(AddressMode.ADDRESS_MODE_ABSOLUTE, -1);
			cycles += 6;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_DEC_ABSOLUTE_X:
			modifyMemory(AddressMode.ADDRESS_MODE_ABSOLUTE_X, -1);
			cycles += 7;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_DEX:
			register_x -= 1;
			setFlags(register_x);
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_DEY:
			register_y -= 1;
			setFlags(register_y);
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_EOR_IMMEDIATE:
			eor(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_EOR_ZERO_PAGE:
			eor(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_EOR_ZERO_PAGE_X:
			eor(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_EOR_ABSOLUTE:
			eor(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_EOR_ABSOLUTE_X:
			eor(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_EOR_ABSOULTE_Y:
			eor(AddressMode.ADDRESS_MODE_ABSOLUTE_Y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_EOR_INDIRECT_X:
			eor(AddressMode.ADDRESS_MODE_INDIRECT_X);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_EOR_INDIRECT_Y:
			eor(AddressMode.ADDRESS_MODE_INDIRECT_Y);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_INC_ZERO_PAGE:
			modifyMemory(AddressMode.ADDRESS_MODE_ZEROPAGE, 1);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_INC_ZERO_PAGE_X:
			modifyMemory(AddressMode.ADDRESS_MODE_ZEROPAGE_X, 1);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_INC_ABSOLUTE:
			modifyMemory(AddressMode.ADDRESS_MODE_ABSOLUTE, 1);
			cycles += 6;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_INC_ABSOLUTE_X:
			modifyMemory(AddressMode.ADDRESS_MODE_ABSOLUTE_X, 1);
			cycles += 7;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_INX:
			register_x += 1;
			setFlags(register_x);
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_INY:
			register_y += 1;
			setFlags(register_y);
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_JMP_ABSOULUTE:
			jmp(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 3;
			instruction_length = 3;
			return;
			
		case Instruction.OPCODE_JMP_INDIRECT:
			jmp(AddressMode.ADDRESS_MODE_INDIRECT);
			cycles += 5;
			instruction_length = 3;
			return;
			
		case Instruction.OPCODE_JSR:
			// TO DO jump
			break;
			
		case Instruction.OPCODE_LDA_IMMEDIATE:
			register_a = load(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDA_ZERO_PAGE:
			register_a = load(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDA_ZERO_PAGE_X:
			register_a = load(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDA_ABSOLUTE:
			register_a = load(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDA_ABSOLUTE_X:
			register_a = load(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDA_ABSOLUTE_Y:
			register_a = load(AddressMode.ADDRESS_MODE_ABSOLUTE_Y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDA_INDIRECT_X:
			register_a = load(AddressMode.ADDRESS_MODE_INDIRECT_X);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDA_INDIRECT_Y:
			register_a = load(AddressMode.ADDRESS_MODE_INDIRECT_Y);
			cycles += 5;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDX_IMMEDIATE:
			register_x = load(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDX_ZERO_PAGE:
			register_x = load(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDX_ZERO_PAGE_Y:
			register_x = load(AddressMode.ADDRESS_MODE_ZEROPAGE_Y);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDX_ABSOLUTE:
			register_x = load(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDX_ABSOLUTE_Y:
			register_x = load(AddressMode.ADDRESS_MODE_ABSOLUTE_Y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDY_IMMEDIATE:
			register_y = load(AddressMode.ADDRESS_MODE_IMMIDIATE);
			cycles += 2;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDY_ZERO_PAGE:
			register_y = load(AddressMode.ADDRESS_MODE_ZEROPAGE);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDY_ZERO_PAGE_X:
			register_y = load(AddressMode.ADDRESS_MODE_ZEROPAGE_X);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_LDY_ABSOLUTE:
			register_y = load(AddressMode.ADDRESS_MODE_ABSOLUTE);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LDY_ABSOLUTE_X:
			register_y = load(AddressMode.ADDRESS_MODE_ABSOLUTE_X);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_LSR_ACCUMULATOR:
			// TO DO
			break;
			
		case Instruction.OPCODE_LSR_ZERO_PAGE:
			// TO DO
			break;
			
		case Instruction.OPCODE_LSR_ZERO_PAGE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_LSR_ABSOLUTE:
			// TO DO
			break;
			
		case Instruction.OPCODE_LSR_ABSOLUTE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_NOP:
			cycles += 2;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_ORA_IMMEDIATE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_ZERO_PAGE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_ZERO_PAGE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_ABSOLUTE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_ABSOLUTE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_ABSOLUTE_Y:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_INDIRECT_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ORA_INDIRECT_Y:
			// TO DO
			break;
			
		case Instruction.OPCODE_PHA:
			push(register_a);
			cycles += 3;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_PHP:
			push(getFlagsByte());
			cycles += 3;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_PLA:
			register_a = pull();
			setFlags(register_a);
			cycles += 4;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_PLP:
			int flagsByte = pull();
			setFlagsByte(flagsByte);
			cycles += 4;
			instruction_length = 1;
			break;
			
		case Instruction.OPCODE_ROL_ACCUMULATOR:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROL_ZERO_PAGE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROL_ZERO_PAGE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROL_ABSOLUTE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROL_ABSOLUTE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROR_ACCUMULATOR:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROR_ZERO_PAGE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROR_ZERO_PAGE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROR_ABSOLUTE:
			// TO DO
			break;
			
		case Instruction.OPCODE_ROR_ABSOLUTE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_RTI:
			// TO DO
			break;
			
		case Instruction.OPCODE_RTS:
			// TO DOs
			break;
			
		//*** SBC
		case Instruction.OPCODE_SBC_IMMEDIATE:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_ZERO_PAGE:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_ZERO_PAGE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_ABSOLUTE:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_ABSOLUTE_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_ABSOLUTE_Y:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_INDIRECT_X:
			// TO DO
			break;
			
		case Instruction.OPCODE_SBC_INDIRECT_Y:
			// TO DO
			break;
			
		//*** SEC
		case Instruction.OPCODE_SEC:
			flag_carry = true;
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** SED
		case Instruction.OPCODE_SED:
			flag_decimal_mode = true;
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** SEI
		case Instruction.OPCODE_SEI:
			flag_interrupt_disable = true;
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** STA
		case Instruction.OPCODE_STA_ZERO_PAGE:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE, register_a);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STA_ZERO_PAGE_X:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE_X, register_a);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STA_ABSOLUTE:
			store(AddressMode.ADDRESS_MODE_ABSOLUTE, register_a);
			cycles += 4;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_STA_ABSOLUTE_X:
			store(AddressMode.ADDRESS_MODE_ABSOLUTE_X, register_a);
			cycles += 5;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_STA_ABSOLUTE_Y:
			store(AddressMode.ADDRESS_MODE_ABSOLUTE_Y, register_a);
			cycles += 5;
			instruction_length = 3;
			break;
			
		case Instruction.OPCODE_STA_INDIRECT_X:
			store(AddressMode.ADDRESS_MODE_INDIRECT_X, register_a);
			cycles += 6;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STA_INDIRECT_Y:
			store(AddressMode.ADDRESS_MODE_INDIRECT_Y, register_a);
			cycles += 6;
			instruction_length = 2;
			break;
			
		//*** STX
		case Instruction.OPCODE_STX_ZERO_PAGE:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE, register_x);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STX_ZERO_PAGE_Y:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE_Y, register_x);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STX_ABSOLUTE:
			store(AddressMode.ADDRESS_MODE_ABSOLUTE, register_x);
			cycles += 4;
			instruction_length = 3;
			break;
			
		//*** STY
		case Instruction.OPCODE_STY_ZERO_PAGE:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE, register_y);
			cycles += 3;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STY_ZERO_PAGE_X:
			store(AddressMode.ADDRESS_MODE_ZEROPAGE_X, register_y);
			cycles += 4;
			instruction_length = 2;
			break;
			
		case Instruction.OPCODE_STY_ABSOLUTE:
			store(AddressMode.ADDRESS_MODE_ABSOLUTE, register_y);
			cycles += 4;
			instruction_length = 3;
			break;
			
		//*** TAX
		case Instruction.OPCODE_TAX:
			register_x = register_a;
			setFlags(register_x);
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** TAY
		case Instruction.OPCODE_TAY:
			register_y = register_a;
			setFlags(register_y);
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** TSX
		case Instruction.OPCODE_TSX:
			register_x = register_sp;
			setFlags(register_x);
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** TXA
		case Instruction.OPCODE_TXA:
			register_a = register_x;
			setFlags(register_a);
			cycles += 2;
			instruction_length = 1;
			break;
			
		//*** TXS
		case Instruction.OPCODE_TXS:
			register_sp = register_x;
			setFlags(register_sp);
			cycles += 2;
			instruction_length = 1;
			break;
		
		//*** TYA
		case Instruction.OPCODE_TYA:
			register_a = register_y;
			setFlags(register_a);
			cycles += 2;
			instruction_length = 1;
			break;
		}
		register_pc += instruction_length;
	}
	
	protected void adc(final int addressMode) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_IMMIDIATE:
			memoryData = programRom.readMemory(register_pc + 1);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			RefBoolean pageCrossedAbX = new RefBoolean(false);
			
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					pageCrossedAbX);
			
			if (pageCrossedAbX.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			RefBoolean pageCrossedAbY = new RefBoolean();
			
			memoryData = readAbsoluteY(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					pageCrossedAbY);
			
			if (pageCrossedAbY.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			memoryData = readIndirectX(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			RefBoolean pageCrossedInY = new RefBoolean();
			
			memoryData = readIndirectY(programRom.readMemory(register_pc + 1), pageCrossedInY);
			
			if (pageCrossedInY.getValue())
				cycles++;
			
			break;

		default:
			break;
		}
		
		int result = register_a + memoryData + (flag_carry ? 1 : 0);
		
		//Check flag 
		flag_carry = result != NumberUtil.roundByte(result) ? true : false;
		flag_overflow = !NumberUtil.getBit( (register_a ^ memoryData) , 7) && 
				NumberUtil.getBit( (register_a ^ result) , 7);
		
		register_a = NumberUtil.roundByte(result);
		
		setFlags(register_a);
	}
	
	
	protected void and(final int addressMode) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_IMMIDIATE:
			memoryData = programRom.readMemory(register_pc + 1);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1),
					programRom.readMemory(register_pc + 2));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			final RefBoolean abXCrossed = new RefBoolean();
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abXCrossed);
			
			if (abXCrossed.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			final RefBoolean abYCrossed = new RefBoolean();
			memoryData = readAbsoluteY(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abYCrossed);
			
			if (abYCrossed.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			memoryData = readIndirectX(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			final RefBoolean idrYCrossed = new RefBoolean();
			memoryData = readIndirectY(programRom.readMemory(register_pc + 1), idrYCrossed);
			
			if (idrYCrossed.getValue())
				cycles++;
			
			break;

		default:
			break;
		}
		
		register_a = register_a & memoryData;
		
		setFlags(register_a);
	}
	

	protected void asl(final int addressMode) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_ACCUMULATOR:
			memoryData = register_a;
			
			memoryData = memoryData << 1;
			
			flag_carry = NumberUtil.roundByte(memoryData) != memoryData;
			
			register_a = NumberUtil.roundByte(memoryData);
			
			memoryData = register_a;
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(programRom.readMemory(register_pc + 1));
			
			memoryData = memoryData << 1;
			
			flag_carry = NumberUtil.roundByte(memoryData) != memoryData;
			
			memoryData = NumberUtil.roundByte(memoryData);
			
			writeZeroPage(programRom.readMemory(register_pc + 1), memoryData);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(programRom.readMemory(register_pc + 1));
			
			memoryData = memoryData << 1;
			
			flag_carry = NumberUtil.roundByte(memoryData) != memoryData;
			
			memoryData = NumberUtil.roundByte(memoryData);
			
			writeZeroPageX(programRom.readMemory(register_pc + 1), memoryData);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			
			memoryData = memoryData << 1;
			
			flag_carry = NumberUtil.roundByte(memoryData) != memoryData;
			
			memoryData = NumberUtil.roundByte(memoryData);
			
			writeAbsolute(programRom.readMemory(register_pc + 1), programRom.readMemory(register_pc + 2), memoryData);
 			break;
 			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					new RefBoolean());
			
			memoryData = memoryData << 1;
			
			flag_carry = NumberUtil.roundByte(memoryData) != memoryData;
			
			memoryData = NumberUtil.roundByte(memoryData);
			
			writeAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					memoryData);
			break;

		default:
			break;
		}
		
		setFlags(memoryData);
	}
	
	
	protected int branch(final boolean condition) {
		if (condition) {
			final byte relative = (byte) programRom.readMemory(register_pc + 1);
			
			if (NumberUtil.checkCrossedPage(register_pc + 2, 
					register_pc + 2 +relative)) {
				cycles += 2;
			} else {
				cycles++;
			}
			
			return relative;
		}
		return 0;
	}
	
	
	protected void bit(final int addressMode) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(programRom.readMemory(register_pc + 1));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;

		default:
			break;
		}
		
		final int result = memoryData & register_a;
		
		flag_overflow = NumberUtil.getBit(result, 6);
		
		setFlags(result);
	}

	
	protected void compare(final int addressMode, final int regValue) {
		int memoryData = 0;
		
		final int operand = programRom.readMemory(register_pc + 1);
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_IMMIDIATE:
			memoryData = operand;
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			final RefBoolean abXCrossed = new RefBoolean();
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abXCrossed);
			
			if (abXCrossed.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			final RefBoolean abYCrossed = new RefBoolean();
			memoryData = readAbsoluteY(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abYCrossed);
			
			if (abYCrossed.getValue())
				cycles++;
			
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			memoryData = readIndirectX(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			final RefBoolean indYCrossed = new RefBoolean();
			
			memoryData = readIndirectY(operand, indYCrossed);
			
			if (indYCrossed.getValue()) 
				cycles++;
			
			break;

		default:
			break;
		}
		
		final int result = regValue - memoryData;
		
		flag_carry = result >= 0;
		
		setFlags(result);
	}
	
	
	protected void modifyMemory(final int addressMode, final int plusWith) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(programRom.readMemory(register_pc + 1));
			memoryData += plusWith;
			writeZeroPage(programRom.readMemory(register_pc + 1), memoryData);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(programRom.readMemory(register_pc + 1));
			memoryData += plusWith;
			writeZeroPageX(programRom.readMemory(register_pc + 1), memoryData);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			memoryData += plusWith;
			writeAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2),
					memoryData);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 1), 
					new RefBoolean());
			memoryData += plusWith;
			writeAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 1), 
					memoryData);
			break;

		default:
			break;
		}
		
		setFlags(memoryData);
	}
	
	
	protected void eor(final int addressMode) {
		int memoryData = 0;
		final int operand = programRom.readMemory(register_pc + 1);
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_IMMIDIATE:
			memoryData = operand;
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			final RefBoolean abXCrossed = new RefBoolean();
			memoryData = readAbsoluteX(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abXCrossed);
			
			if (abXCrossed.getValue())
				cycles += 1;
			
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			final RefBoolean abYCrossed = new RefBoolean();
			memoryData = readAbsoluteY(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2), 
					abYCrossed);
			
			if (abYCrossed.getValue())
				cycles += 1;
			
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			memoryData = readIndirectX(operand);
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			final RefBoolean indYCrossed = new RefBoolean();
			memoryData = readIndirectY(operand, indYCrossed);
			
			if (indYCrossed.getValue())
				cycles += 1;
			
			break;

		default:
			break;
		}
		
		register_a = register_a ^ memoryData;
		
		setFlags(register_a);
	}
	
	protected void jmp(final int addressMode) {
		int memoryData = 0;
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT:
			memoryData = readIndirect(programRom.readMemory(register_pc + 1), 
					programRom.readMemory(register_pc + 2));
			break;
		}
		
		register_pc = memoryData;
	}

	/**
	 * Load data from memory. It is used for load to register instructions
	 * @param addressMode
	 * @return
	 */
	protected int load(final int addressMode) {
		int memoryData = -1;
		
		final int operand1 = programRom.readMemory(register_pc + 1);
		final int operand2 = programRom.readMemory(register_pc + 2);
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_IMMIDIATE:
			memoryData = operand1;
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			memoryData = readZeroPage(operand1);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			memoryData = readZeroPageX(operand1);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_Y:
			memoryData = readZeroPageY(operand1);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			memoryData = readAbsolute(operand1, operand2);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			final RefBoolean abXCrossed = new RefBoolean();
			memoryData = readAbsoluteX(operand1, operand2, abXCrossed);
			
			if (abXCrossed.getValue())
				cycles += 1;
			
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			final RefBoolean abYCrossed = new RefBoolean();
			memoryData = readAbsoluteY(operand1, operand2, abYCrossed);
			
			if (abYCrossed.getValue())
				cycles += 1;
			
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			memoryData = readIndirectX(operand1);
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			final RefBoolean indYCrossed = new RefBoolean();
			memoryData = readIndirectY(operand1, indYCrossed);
			
			if (indYCrossed.getValue())
				cycles += 1;
			
			break;

		default:
			System.out.println("6502 doesn't support this Addressing Mode");
			break;
		}
		
		setFlags(memoryData);
		
		return memoryData;
	}
	
	/**
	 * Store value to data. Used for store data instructions
	 * @param addressMode
	 * @param value
	 */
	protected void store(final int addressMode, final int value) {
		final int operand1 = programRom.readMemory(register_pc + 1);
		final int operand2 = programRom.readMemory(register_pc + 2);
		
		switch (addressMode) {
		case AddressMode.ADDRESS_MODE_ZEROPAGE:
			writeZeroPage(operand1, value);
			break;
			
		case AddressMode.ADDRESS_MODE_ZEROPAGE_X:
			writeZeroPageX(operand1, value);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE:
			writeAbsolute(operand1, operand2, value);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_X:
			writeAbsoluteX(operand1, operand2, value);
			break;
			
		case AddressMode.ADDRESS_MODE_ABSOLUTE_Y:
			writeAbsoluteY(operand1, operand2, value);
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_X:
			writeIndirectX(operand1, value);
			break;
			
		case AddressMode.ADDRESS_MODE_INDIRECT_Y:
			writeIndirectY(operand1, value);
			break;
			
		}
	}
	
	/**
	 * Set zero flag and negative flag 
	 * @param result
	 */
	protected void setFlags(final int result) {
		flag_zero = result == 0;
		flag_negative = NumberUtil.getBit(result, 7);
	}
	
	
	//STACK PROCESSING
	protected void push(final int data) {
		ram.writeMemory(0x0100 + register_sp, data);
		register_sp--;
		register_sp = NumberUtil.roundByte(register_sp);
	}
	
	protected int pull() {
		final int value = ram.readMemory(0x0100 + register_sp);
		register_sp++;
		register_sp = NumberUtil.roundByte(register_sp);
		
		return value;
	}

}
